package top.guoziyang.mydb.backend.dm.pageCache;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import top.guoziyang.mydb.backend.common.AbstractCache;
import top.guoziyang.mydb.backend.dm.page.Page;
import top.guoziyang.mydb.backend.dm.page.PageImpl;
import top.guoziyang.mydb.backend.utils.Panic;
import top.guoziyang.mydb.common.Error;


/**
 *  页面缓存的具体实现类,需要继承抽象缓存框架，并且实现 getForCache() 和 releaseForCache() 两个抽象方法
 * */
public class PageCacheImpl extends AbstractCache<Page> implements PageCache {
    
    private static final int MEM_MIN_LIM = 10;
    public static final String DB_SUFFIX = ".db";

    private RandomAccessFile file;
    private FileChannel fc;
    private Lock fileLock;

    /***
     * AtomicInteger是Java中提供的一个原子变量类，用于在多线程环境中安全地操作整数值。
     * AtomicInteger使用CAS（Compare And Swap）来确保在多线程环境下对整数值的是原子机制的，不需要加修改锁（比如synchronized）来避免强制。
     * 方法:
     * incrementAndGet()：原子引发当前值加一。
     * decrementAndGet()：原子发射当前值减一。
     * getAndSet(int newValue)：将当前值设置为给定值，并返回旧值。
     * compareAndSet(int expect, int update)：如果当前值等于预期值expect，则将其设置为update值。
     */
    private AtomicInteger pageNumbers;

    PageCacheImpl(RandomAccessFile file, FileChannel fileChannel, int maxResource) {
        // maxResource代表缓存的最大缓存资源数（数据页的数量）
        super(maxResource);
        if(maxResource < MEM_MIN_LIM) {
            Panic.panic(Error.MemTooSmallException);
        }
        long length = 0;
        try {
            length = file.length();
        } catch (IOException e) {
            Panic.panic(e);
        }
        this.file = file;
        this.fc = fileChannel;
        this.fileLock = new ReentrantLock();
        this.pageNumbers = new AtomicInteger((int)length / PAGE_SIZE);
    }


    /***
     *  创建新数据页,存放数据
     */
    public int newPage(byte[] initData) {
        int pgno = pageNumbers.incrementAndGet(); //新数据页的页号
        Page pg = new PageImpl(pgno, initData, null);
        flush(pg); // 新建的页面需要立刻刷盘
        return pgno;
    }

    /***
     *  从缓存中读取数据页
     */
    public Page getPage(int pgno) throws Exception {
        return get((long)pgno);
    }

    /**
     * 根据pageNumber从数据库文件中读取页数据放入缓存，裹成Page对象并返回
     */
    @Override
    protected Page getForCache(long key) throws Exception {
        int pgno = (int)key;
        long offset = PageCacheImpl.pageOffset(pgno);

        // 创建容量为 PAGE_SIZE字节 的新缓冲区
        ByteBuffer buf = ByteBuffer.allocate(PAGE_SIZE);
        fileLock.lock();
        try {
            fc.position(offset);
            fc.read(buf);
        } catch(IOException e) {
            Panic.panic(e);
        }
        fileLock.unlock();
        return new PageImpl(pgno, buf.array(), this);
    }

    /**
     * 当数据页被驱逐时，如果是脏页，则进行持久化
     */
    @Override
    protected void releaseForCache(Page pg) {
        if(pg.isDirty()) {
            // 如果是脏页，落盘,然后标记为 非脏页
            flush(pg);
            pg.setDirty(false);
        }
    }

    /**
     * 释放数据页缓存
     */
    public void release(Page page) {
        release((long)page.getPageNumber());
    }

    /**
     * 把该数据页持久化到磁盘
     */
    public void flushPage(Page pg) {
        flush(pg);
    }

    /**
     * 将Page对象的数据刷新到磁盘中，以保证页面的内容持久化。
     */
    private void flush(Page pg) {
        int pgno = pg.getPageNumber();
        long offset = pageOffset(pgno);

        fileLock.lock();
        try {
            ByteBuffer buf = ByteBuffer.wrap(pg.getData());
            fc.position(offset);
            fc.write(buf); //将ByteBuffer中的数据读取到文件通道的当前位置
            //强制将数据刷新到磁盘，确保数据的持久化。
            // false参数表示不需要同步文件元数据(如文件修改时间、大小等)，仅同步文件内容。
            fc.force(false);
        } catch(IOException e) {
            Panic.panic(e);
        } finally {
            fileLock.unlock();
        }
    }

    /**
     * 截断文件到指定的页面大小，删除因崩溃导致未完全写入的页面
     */
    public void truncateByPgno(int maxPgno) {
        long size = pageOffset(maxPgno + 1);
        try {
            // file.setLength(size)用于调整文件大小，超出size部分的数据会被删除。
            file.setLength(size);
        } catch (IOException e) {
            Panic.panic(e);
        }
        //更新pageNumbers（页面数）为maxPgno，以同步文件实际大小和页面数
        pageNumbers.set(maxPgno);
    }

    /**
     * 重写close()方法，在此基础上关掉fc和file
     */
    @Override
    public void close() {
        super.close();
        try {
            fc.close();
            file.close();
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    /**
     * 获取当前pageNumbers的整数值
     */
    public int getPageNumber() {
        return pageNumbers.intValue();
    }

    // 获取当前页面的偏移量
    private static long pageOffset(int pgno) {
        return (pgno-1) * PAGE_SIZE;
    }
    
}
